﻿using Nemerle.Utility;
using Nemerle.Assertions;

using System;
using System.Collections.Generic;
using System.Linq;

namespace Nemerle.Data
{
  public interface IMeasurable[I]
  {
    Length : I { get; }
  }
  
  public interface IBounded[I]
  {
    LowerBound : I {get;}
    UpperBound : I {get;}
    InRange(i : I) : bool
  }
  
  public interface IIndexed[T, I]
  {
    Item[i : I] : T {get;}
  }
  
  public interface ISlice[T] : /*IBounded[int],*/ IIndexed[T, int], IMeasurable[int], IEnumerable[T]
  {
    Slice(lb : int, ub : int) : ISlice[T];
    Append(slice : ISlice[T]) : ISlice[T];
    Prepend(slice : ISlice[T]) : ISlice[T];
  }
  
  public module Slices
  {
    public Slice[T](this arr : array[T], lowerBound : int, upperBound : int) : ISlice[T]
    {
      ArraySlice(arr, lowerBound, upperBound)
    }
    
    public Concatenate[T](this s1 : ISlice[T], s2 : ISlice[T]) : ISlice[T]
    {
      SlicePlus(s1, s2)
    }
    
    #warning Not tested
    public Flatten[T](this slice : ISlice[T]) : Seq[array[T]*int*int]
    {
      | s is ArraySlice[T] => Seq.Single((s.Array, s.LowerBound, s.UpperBound))
      | s is SlicePlus[T]  => Flatten(s.Left).ConcatenateSeq(Flatten(s.Right))
    }
    
    #warning Not tested
    public Force[T](this slice : ISlice[T]) : array[T]
    {
      def slices = slice.Flatten();
      mutable sLength = 0;
      foreach((_,l,u) in slices)
        sLength += (u - l + 1);
      def result = array(sLength);
      mutable cur = 0;
      foreach((arr,l,u) in slices)
      {
        def len = u - l + 1;
        Array.Copy(arr, l, result, cur, len);
        cur += arr.Length;
      }
      result
    }
  }
  
  #warning Not tested
  [Record]
  public class ArraySlice[T] : ISlice[T], IEnumerable[T]
  {
    [Accessor] _array : array[T];
    [Accessor] _lowerBound : int;
    [Accessor] _upperBound : int;
    
    public Slice(lb : int, ub : int) : ISlice[T]
    requires lb <= ub
    requires lb >= 0
    {
      ArraySlice(_array, _lowerBound + lb, _lowerBound + ub)
    }
    
    public Append([NotNull] slice : ISlice[T]) : ISlice[T]
    {
      SlicePlus(this,slice)
    }
    
    public Prepend([NotNull] slice : ISlice[T]) : ISlice[T]
    {
      SlicePlus(slice,this)
    }
    
    public GetEnumerator() : IEnumerator[T]
    {
      foreach(i in [_lowerBound .. _upperBound]) yield _array[i]
    }

    public Length : int
    {
        get { _upperBound - _lowerBound + 1 }
    }
    
    //public LowerBound : int {get {0};}
    //public UpperBound : int {get {Length - 1};}
    //public InRange(i : int) : bool {i >= LowerBound && i <= UpperBound}

    public Item[index : int] : T
    {
        get { _array[_lowerBound + index] }
    }
  }
  
  #warning Not tested
  [Record]
  public class IndexedSlice[T] : ISlice[T]
  {
    _slice : ISlice[T];
    [Accessor] _lowerBound : int;
    [Accessor] _upperBound : int;
    
    public Slice(lb : int, ub : int) : ISlice[T]
    requires lb <= ub
    {
      IndexedSlice(_slice, _lowerBound + lb, _lowerBound + ub)
    }
    
    public Append(slice : ISlice[T]) : ISlice[T]
    {
      SlicePlus(this,slice)
    }
    
    public Prepend(slice : ISlice[T]) : ISlice[T]
    {
      SlicePlus(slice,this)
    }
    
    public GetEnumerator() : IEnumerator[T]
    {
      foreach(i in [_lowerBound .. _upperBound]) yield _slice.Item[i]
    }

    public Length : int
    {
        get { _upperBound - _lowerBound + 1 }
    }

    public Item[index : int] : T
    {
        get { _slice.Item[_lowerBound + index] }
    }
  }
  
  #warning Not tested
  [Record]
  public class SlicePlus[T] : ISlice[T]
  {
    [Accessor] _left  : ISlice[T];
    [Accessor] _right : ISlice[T];
    
    public Slice(lb : int, ub : int) : ISlice[T]
    requires lb <= ub
    requires lb >= 0
    {
      | (l, u) when l >= _left.Length => _right.Slice(l - _left.Length, u - _left.Length)
      | (l, u) when u <= _left.Length => _left.Slice(l, u)
      | (l, u)                        => SlicePlus(_left.Slice(l, _left.Length-1), _right.Slice(0, u - _left.Length))      
    }
    
    public Append([NotNull] slice : ISlice[T]) : ISlice[T]
    {
      SlicePlus(this,slice)
    }
    
    public Prepend([NotNull] slice : ISlice[T]) : ISlice[T]
    {
      SlicePlus(slice,this)
    }
    
    public GetEnumerator() : IEnumerator[T]
    {
      _left.ConcatenateSeq(_right).GetEnumerator()
    }

    public Length : int
    {
        get { _left.Length + _right.Length }
    }

    public Item[index : int] : T
    {
        get {
          if(index < _left.Length) _left.Item[index]
          else _right.Item[index - _left.Length]
        }
    }
  }
}
